package fr.vvins.core.common.dto;

import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

public class DtoProperty implements Serializable {

	private static final long serialVersionUID = 3621073458417316477L;
	private String name;
    private boolean isCollection;
    private boolean isMap;
    private boolean isArray;
    private Class<?> type;
    //private Class<?> dataType;
    private DtoProperty[] dataTypes;

    public DtoProperty() {
    }
    
    public DtoProperty(Type target) {
    	name="unnamed";
    	if(ParameterizedType.class.isAssignableFrom(target.getClass())){
    		ParameterizedType paramType = (ParameterizedType)target;
    		type = (Class<?>) paramType.getRawType();
    		if (Collection.class.isAssignableFrom(type)) {
                isCollection = true;
                dataTypes = getParameterizedType(paramType.getActualTypeArguments());
            }
            if (Map.class.isAssignableFrom(type)) {
                isMap = true;
                dataTypes = getParameterizedType(paramType.getActualTypeArguments());
            }
            if (type.isArray()) {
                isArray = true;
            }
    	} else {
    		type = (Class)target;
    	}
    }
    
    public DtoProperty(PropertyDescriptor desc) {
        name = desc.getName();
        type = desc.getPropertyType();

        if (Collection.class.isAssignableFrom(type)) {
            isCollection = true;
            dataTypes = getParameterizedType(desc);
        }
        if (Map.class.isAssignableFrom(type)) {
            isMap = true;
            dataTypes = getParameterizedType(desc);
        }
        if (type.isArray()) {
            isArray = true;
        }
    }

    @SuppressWarnings("rawtypes")
    public DtoProperty(Field field) {
        name = field.getName();
        type = field.getType();

        if (Collection.class.isAssignableFrom(type)) {
            isCollection = true;
            dataTypes = getParameterizedType(field);
        }
        if (Map.class.isAssignableFrom(type)) {
            isMap = true;
            dataTypes = getParameterizedType(field);
        }
        if (type.isArray()) {
            isArray = true;
        }
    }
    
    private DtoProperty[] getParameterizedType(Type[] types){
    	List<DtoProperty> properties = new ArrayList<DtoProperty>();
    	for(Type t : types){
    		properties.add(new DtoProperty(t));
    	}
    	if(properties.isEmpty()){
    		return null;
    	}
    	DtoProperty[] propertiesArray = new DtoProperty[properties.size()];
    	properties.toArray(propertiesArray);
    	return propertiesArray;
    }

	private DtoProperty[] getParameterizedType(Field field) {
		DtoProperty[] properties = null;
		if (Collection.class.isAssignableFrom(type.getClass())) {
            Type fieldType = field.getGenericType();
            if (fieldType instanceof ParameterizedType) {
                Type[] types = ((ParameterizedType) fieldType).getActualTypeArguments();
                if (types != null && types.length > 0) {
                    properties = new DtoProperty[1];
                    properties[0] = new DtoProperty(types[0]);
                 }
            }
        }
        if (Map.class.isAssignableFrom(type)) {
            Type fieldType = field.getGenericType();
            if (fieldType instanceof ParameterizedType) {
                Type[] types = ((ParameterizedType) fieldType).getActualTypeArguments();
                if (types != null && types.length > 1) {
                		properties = new DtoProperty[2];
                		properties[0] = new DtoProperty(types[0]);
                		properties[1] = new DtoProperty(types[1]);
                }
            }
        }
        return properties;
	}

  


    @SuppressWarnings("rawtypes")
    protected DtoProperty[] getParameterizedType(PropertyDescriptor currentDescriptor) {
        DtoProperty[] properties = null;
    	Method readMethod = currentDescriptor.getReadMethod();

        // Existe-t-il une m�thode de lecture pour la propri�t� coutante ?
        // Et si oui, cette m�thode renvoie-t-elle une sous classe de List ?
        if (readMethod != null) {
            // Est-ce que le type retour de la m�thode de lecture est un type
            // param�tr� ?
            if (readMethod.getGenericReturnType() instanceof ParameterizedType) {
                // Si oui, on cherche quel est le type param�tr� en question.
                ParameterizedType parameterizedReturnType = (ParameterizedType) readMethod.getGenericReturnType();
                Type[] actualTypeArguments = parameterizedReturnType.getActualTypeArguments();
                // S'il n'y a qu'un seul argument au type param�tr� et si
                // celui-ci correspond au type Dto
                if (Collection.class.isAssignableFrom(currentDescriptor.getPropertyType()) && actualTypeArguments.length == 1) {
                	properties = new DtoProperty[1];
                	properties[0] = new DtoProperty(actualTypeArguments[0]);
                } else if ((Map.class.isAssignableFrom(currentDescriptor.getPropertyType()))
                        && actualTypeArguments.length == 2 ) {
                	properties = new DtoProperty[2];
                	properties[0] = new DtoProperty(actualTypeArguments[0]);
                	properties[1] = new DtoProperty(actualTypeArguments[1]);
                }
            }
        }
        return properties;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public boolean isCollection() {
        return isCollection;
    }

    public void setCollection(boolean isCollection) {
        this.isCollection = isCollection;
    }

    public Class<?> getType() {
        return type;
    }

    public void setType(Class<?> type) {
        this.type = type;
    }

    public boolean isMap() {
        return isMap;
    }

    public void setMap(boolean isMap) {
        this.isMap = isMap;
    }

    public boolean isArray() {
        return isArray;
    }

    public void setArray(boolean isArray) {
        this.isArray = isArray;
    }

    @Override
    public String toString() {
        return getName() + " : " + getType().getName();
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((dataTypes == null) ? 0 : dataTypes.hashCode());
        result = prime * result + (isArray ? 1231 : 1237);
        result = prime * result + (isCollection ? 1231 : 1237);
        result = prime * result + (isMap ? 1231 : 1237);
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        result = prime * result + ((type == null) ? 0 : type.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        DtoProperty other = (DtoProperty) obj;
        if (dataTypes == null) {
            if (other.dataTypes != null) {
                return false;
            }
        } else if (!dataTypes.equals(other.dataTypes)) {
            return false;
        }
        if (isArray != other.isArray) {
            return false;
        }
        if (isCollection != other.isCollection) {
            return false;
        }
        if (isMap != other.isMap) {
            return false;
        }
        if (name == null) {
            if (other.name != null) {
                return false;
            }
        } else if (!name.equals(other.name)) {
            return false;
        }
        if (type == null) {
            if (other.type != null) {
                return false;
            }
        } else if (!type.equals(other.type)) {
            return false;
        }
        return true;
    }

	public DtoProperty[] getDataTypes() {
		return dataTypes;
	}

	public void setDataTypes(DtoProperty[] dataTypes) {
		this.dataTypes = dataTypes;
	}
}
